package ch.janotta.slawo.ultraquest.encounters;

import ch.janotta.slawo.ultraquest.encounters.trainer.TrainingFactory;
import ch.janotta.slawo.ultraquest.items.ItemFactory;
import ch.janotta.slawo.ultraquest.rulesets.areas.AreaRuleset;
import ch.janotta.slawo.ultraquest.rulesets.areas.AreaRulesetsFactory;
import ch.janotta.slawo.ultraquest.types.Area;
import ch.janotta.slawo.ultraquest.types.Encounter;
import ch.janotta.slawo.ultraquest.types.EncounterType;
import ch.janotta.slawo.ultraquest.types.Item;
import ch.janotta.slawo.ultraquest.utilities.Dice;

public final class EncounterFactory {
  /*
   * The algorithm has the following base probabilities:
   * AreaType        EASY         STANDARD    CHALLANGING DIFFICULT   DANGEROUS   DEADLY
   * OVERLAND        90,99        80,99       75,95       60,90       55,85       50,75
   * DUNGEON         80,95        70,90       65,85       50,80       45,75       25,75
   */
  private static final int[][] OVERLAND_TYPE_PROBABILITIES = new int[][] {
    {10, 20}, {9, 18}, {8, 16}, {7, 17}, {6, 12}, {5, 10}
  };
  private static final int[][] DUNGEON_TYPE_PROBABILITIES = new int[][] {
    {6, 12}, {5, 10}, {4, 8}, {3, 6}, {2, 4}, {1, 2}
  };
  private static final int[] ENEMY_STRENGTHS = new int[] {
    4, 5, 6, 8, 9, 10
  };

  public static Encounter getRandomEncounter(Area area) {
    Encounter randomEncounter = null;
    AreaRuleset ruleset = AreaRulesetsFactory.getRulesetInstance(area);
    EncounterType type = getRandomType(ruleset);
    if (type == EncounterType.ENEMY) {
      randomEncounter = prepareRandomEnemyEncounter(ruleset);
    } else if (type == EncounterType.MERCHANT) {
      randomEncounter = prepareRandomMerchantEncounter(area, ruleset);
    } else if (type == EncounterType.TRAINER) {
      randomEncounter = prepareRandomTrainerEncounter(ruleset);
    } else {
      randomEncounter = prepareRandomEnemyEncounter(ruleset);
    }

    return randomEncounter;
  }

  private static Encounter prepareRandomEnemyEncounter(AreaRuleset ruleset) {
    Encounter encounter = new Encounter();
    encounter.setType(EncounterType.ENEMY);
    int n = Dice.getRandomNumber(getBaseAreaValue(ruleset));
    int power = n * 6;
    encounter.setNumber(n);
    encounter.setPower(Dice.getND6PlusM(n, 0));
    encounter.setName("Enemy Encounter");
    encounter.setDescription(n + " enem" + (n == 1 ? "y" : "ies"));
    encounter.setGold((power * 10) / 10);
    return encounter;
  }

  private static Encounter prepareRandomMerchantEncounter(Area area, AreaRuleset ruleset) {
    Encounter encounter = new Encounter();
    encounter.setType(EncounterType.MERCHANT);
    encounter.setNumber(1);
    encounter.setPower(0);
    encounter.setName("Merchant");
    encounter.setDescription("Merchant");
    Item randomItem = ItemFactory.getRandomItem(area);
    encounter.setItem(randomItem);
    encounter.setGold(randomItem.getGoldValue());
    return encounter;
  }

  private static Encounter prepareRandomTrainerEncounter(AreaRuleset ruleset) {
    Encounter encounter = new Encounter();
    encounter.setType(EncounterType.TRAINER);
    encounter.setNumber(1);
    encounter.setPower(0);
    String trainer = TrainingFactory.getRandomTrainingKey();
    encounter.setName(trainer + " Trainer");
    encounter.setDescription(trainer + " Trainer");
    encounter.setTraining(TrainingFactory.getTraining(trainer));
    encounter.setGold(getBaseAreaValue(ruleset) * 10);
    return encounter;
  }

  private static EncounterType getRandomType(AreaRuleset ruleset) {
    EncounterType type = EncounterType.ENEMY;

    int percentage = Dice.getRandomNumber(100);
    if (ruleset.getDungeonSetting() == false) {
      switch (ruleset.getDifficulty()) {
        case EASY:
          type = getType(percentage, OVERLAND_TYPE_PROBABILITIES[0]);
          break;
        case STANDARD:
          type = getType(percentage, OVERLAND_TYPE_PROBABILITIES[1]);
          break;
        case CHALLENGING:
          type = getType(percentage, OVERLAND_TYPE_PROBABILITIES[2]);
          break;
        case DIFFICULT:
          type = getType(percentage, OVERLAND_TYPE_PROBABILITIES[3]);
          break;
        case DANGEROUS:
          type = getType(percentage, OVERLAND_TYPE_PROBABILITIES[4]);
          break;
        case DEADLY:
          type = getType(percentage, OVERLAND_TYPE_PROBABILITIES[5]);
          break;
        default:
          type = EncounterType.ENEMY;
      }
    } else {
      switch (ruleset.getDifficulty()) {
        case EASY:
          type = getType(percentage, DUNGEON_TYPE_PROBABILITIES[0]);
          break;
        case STANDARD:
          type = getType(percentage, DUNGEON_TYPE_PROBABILITIES[1]);
          break;
        case CHALLENGING:
          type = getType(percentage, DUNGEON_TYPE_PROBABILITIES[2]);
          break;
        case DIFFICULT:
          type = getType(percentage, DUNGEON_TYPE_PROBABILITIES[3]);
          break;
        case DANGEROUS:
          type = getType(percentage, DUNGEON_TYPE_PROBABILITIES[4]);
          break;
        case DEADLY:
          type = getType(percentage, DUNGEON_TYPE_PROBABILITIES[5]);
          break;
        default:
          type = EncounterType.ENEMY;
      }
    }

    return type;
  }

  private static EncounterType getType(int percentage, int[] probabilities) {
    if (percentage <= probabilities[0]) return EncounterType.MERCHANT;
    if (percentage <= probabilities[1]) return EncounterType.TRAINER;
    return EncounterType.ENEMY;
  }

  private static int getBaseAreaValue(AreaRuleset ruleset) {
    int baseAreaValue = 1;
    switch (ruleset.getDifficulty()) {
      case EASY:
        baseAreaValue = ENEMY_STRENGTHS[0];
        break;
      case STANDARD:
        baseAreaValue = ENEMY_STRENGTHS[1];
        break;
      case CHALLENGING:
        baseAreaValue = ENEMY_STRENGTHS[2];
        break;
      case DIFFICULT:
        baseAreaValue = ENEMY_STRENGTHS[3];
        break;
      case DANGEROUS:
        baseAreaValue = ENEMY_STRENGTHS[4];
        break;
      case DEADLY:
        baseAreaValue = ENEMY_STRENGTHS[5];
        break;
      default:
        baseAreaValue = ENEMY_STRENGTHS[6];
    }
    if (ruleset.getDungeonSetting() == true) {
      baseAreaValue *= 2;
    }
    return baseAreaValue;
  }

  private static int getPowerOfEnemies(int number, AreaRuleset ruleset) {
    int baseStrength = getBaseAreaValue(ruleset);
    int minStrength = (baseStrength * 5) / 10;
    int maxStrength = (baseStrength * 15) / 10;
    int strength = (Dice.getRandomNumber(minStrength, maxStrength) * 10) / 35;
    int power = strength / number;
    return Dice.getND6PlusM(number * power, 0);
  }
}
